Rust基础[part9]_返回值和错误处理、模块化

Rust基础[part9]_返回值和错误处理、模块化
SoniaChenRust基础[part9]_返回值和错误处理、模块化
返回值
Option<T>
基本使用
fn option_example() {
// 创建Option
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
//使用
let x = plus_one(some_number);
let y = plus_one(absent_number);
println!("x: {:?}, y: {:?}", x, y);
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
辅助函数
- unwrap() : 提取option中的值,但是没有值的时候会panic
fn unwrap_example() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
println!("some_number: {:?}", some_number.unwrap());
println!("some_string: {:?}", some_string.unwrap());
// println!("absent_number: {:?}", absent_number.unwrap());
}
- is_some() 和is_none()
fn is_some_example() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
println!("some_number: {:?}", some_number.is_some());
println!("some_string: {:?}", some_string.is_some());
println!("absent_number: {:?}", absent_number.is_some());
}
fn is_none_example() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
println!("some_number: {:?}", some_number.is_none());
println!("some_string: {:?}", some_string.is_none());
println!("absent_number: {:?}", absent_number.is_none());
}
错误处理
Rust中的错误主要分为两类:
- 可恢复错误:通常用于系统全局角度来看可以接受的错误,例如处理用户的访问、操作等错误,这些错误只会影响某个用户自身的操作进程,而不会对系统的全局稳定性产生影响 Result<T,E>
- 不可恢复错误:全局性或系统性的错误,会比较致命 Panic
Panic
panic!
是 Rust 标准库提供的宏,用于主动引发运行时错误。- 当
panic!
被调用时:- 程序打印错误信息(包括文件名、行号等)。
- 展开(unwind)调用栈并清理资源(默认行为)。
- 最终终止当前线程或整个程序(取决于编译配置)。
常见场景
场景 | 描述 |
---|---|
panic! 显式调用 |
主动触发 panic |
数组越界访问 | 如 vec[100] 访问长度不足的向量 |
unwrap() 或 expect() 错误 |
当 Option 或 Result 为 None 或 Err 时 |
断言失败 | 使用 assert! , assert_eq! 等宏检查失败时 |
行为控制
1. 栈展开(Unwinding)
默认情况下,panic 会:
- 展开调用栈(stack unwinding)
- 执行析构函数(drop)
- 清理资源
这是最安全的方式,但可能带来一定性能开销。
2. 中止(Aborting)
在 [Cargo.toml](file:///Users/tinachan/rust/hello_cargo/Cargo.toml) 中可以配置:
[profile.release]
panic = "abort"
"abort"
:直接终止程序,不进行栈展开(适用于嵌入式系统或性能敏感场景)"unwind"
:默认值,保留栈展开行为
示例代码
示例 1:显式 panic
fn main() {
panic!("这是一个主动 panic");
}
示例 2:数组越界访问
let v = vec![1, 2, 3];
println!("{}", v[99]); // 越界访问触发 panic
示例 3:使用 unwrap()
触发 panic
let s: Option<String> = None;
let _ = s.unwrap(); // 触发 panic
获取 Backtrace(调试信息)
设置环境变量以获取详细的调用栈信息:
RUST_BACKTRACE=1 cargo run
输出将包含完整的调用栈,帮助定位 panic 发生的位置。
panic 处理策略(高级)
你可以通过 std::panic::set_hook
自定义 panic 处理逻辑:
use std::panic;
panic::set_hook(Box::new(|info| {
println!("自定义 panic 处理:{}", info);
}));
panic!("测试自定义 panic hook");
注意:此方法只能设置一次,通常用于日志记录或崩溃分析工具集成。
Result
在 Rust 中,Result
是一个标准库提供的枚举类型,用于处理可恢复错误(recoverable errors)。与 panic!
不同,Result
允许开发者通过返回值明确地处理成功或失败的情况,是编写健壮程序的核心机制之一。
定义
enum Result<T, E> {
Ok(T),
Err(E),
}
T
:表示操作成功时返回的值类型。E
:表示操作失败时返回的错误类型。
二、常用操作
1. match
匹配
最基础也最灵活的方式:
#[test]
fn result_example() {
match divide(19, 0) {
Ok(result) => println!("result={}", result),
Err(err) => println!("{}", err),
};
}
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("除数不能为0".to_string())
} else {
Ok(a / b)
}
}
2. 提取内部值: unwrap()
和 expect(msg)
unwrap()
:如果Ok
返回内部值;如果是Err
则触发 panic。expect(&str)
:类似unwrap()
,但可以自定义 panic 消息。
let result = divide(4, 0).unwrap(); // 如果出错会 panic
let result = divide(4, 0).expect("除法计算错误"); // 自定义 panic 消息
⚠️ 注意:仅在测试或确定不会出错的情况下使用。
3. 传播错误:?
运算符
用于在函数中快速传播错误。只能用于返回类型为 Result
的函数中。
fn read_file() -> Result<String, std::io::Error> {
let content = std::fs::read_to_string("file.txt")?;
Ok(content)
}
4. 对内部值进行修改:map
、map_err
和 and_then
map(f)
:对Ok
值进行映射,不影响Err
。map_err(f)
: 可以修改Err
类型and_then(f)
: 链式调用,只有当前为Ok
时才继续执行。
let result = divide(10, 2)
.map(|x| x * 2)
.and_then(|x| divide(x, 0));
5. or
和 or_else
or(res)
:如果当前是Err
,则使用给定的Result
替代。or_else(f)
:如果当前是Err
,则调用闭包获取替代值。
let res = divide(4, 0).or(Ok(42)); // 如果出错就返回默认值 42
6. is_ok
/ is_err
/ ok()
/ err()
用于判断状态或转换为 Option
。
if result.is_ok() {
println!("操作成功");
}
let opt = result.ok(); // 转换为 Option<T>
7. 对返回的错误进行处理 err.kind
use std::fs::File;
use std::io::ErrorKind;
fn error_kind_example() {
let greeting = File::open("hello.txt");
let greeting_result = match greeting {
Ok(result) => result,
Err(err) => match err.kind() {
ErrorKind::NotFound => File::create("hello.txt").unwrap(),
other_error => panic!("Error: {}", err),
},
};
}
三、注意事项
-
避免滥用
unwrap()
和expect()
:除非你非常确定某个操作不会失败,否则应优先使用match
或?
来处理错误。 -
错误类型保持一致:在一个项目中,建议统一使用相同的错误类型(如自定义枚举或
anyhow::Error
),以便集中处理。 -
使用
?
时注意函数签名:只能在返回Result
或Option
的函数中使用?
,否则编译器会报错。 -
错误信息应清晰具体:不要简单返回
"error"
,而应该提供上下文信息,便于调试和日志分析。 -
配合
From
trait 自动转换错误:你可以为自定义错误类型实现From
,从而简化错误传播。
impl From<std::io::Error> for MyError {
fn from(e: std::io::Error) -> Self {
MyError::Io(e)
}
}
这样就可以直接使用 ?
将 std::io::Result
转换为你的错误类型。
操作 | 用途 |
---|---|
match |
手动匹配成功或失败情况 |
unwrap() / expect() |
快速获取值,出错 panic |
? |
向上层传递错误 |
map() / and_then() |
链式处理成功值 |
or() / or_else() |
提供备选错误处理方案 |
panic 与 Result
的区别
特性 | panic! |
Result |
---|---|---|
类型 | 不可恢复错误 | 可恢复错误 |
推荐使用场景 | 逻辑错误、非法状态 | 文件读写、网络请求等 |
是否必须处理 | 否 | 是 |
对性能影响 | 较大(栈展开) | 小 |
最佳实践 |
---|
优先使用 Result 而不是 panic! |
减少 unwrap() 的使用,尤其在生产代码中 |
统一错误类型,方便集中处理 |
使用 ? 简化错误传播逻辑 |
练习
// 修复call函数的错误
// 当b为None时,按默认值1
fn call(a: i32, b: Option<i32>) -> Result<f64, String> {
let b = b.unwrap_or(1); // 处理b为None的情况,默认值1
let r = match divide(a, b) {
Some(r) => r,
None => return Err(String::from("Division by zero")), // 捕获除零错误
};
let s = match sqrt(r) {
Ok(s) => s,
Err(MathError::NegativeSquareRoot) => {
return Err(String::from("Cannot compute square root of a negative number")) // 捕获负数平方根错误
}
};
Ok(s) // 正常返回结果
}
fn divide(a: i32, b: i32) -> Option<f64> {
if b != 0 {
Some(a as f64 / b as f64)
} else {
None
}
}
pub enum MathError {
DivisionByZero,
NegativeSquareRoot,
}
fn sqrt(x: f64) -> Result<f64, MathError> {
if x < 0.0 {
Err(MathError::NegativeSquareRoot)
} else {
Ok(x.sqrt())
}
}
模块化 Modules
Rust 的模块系统用于组织代码结构、控制作用域和可见性。它支持将程序划分为多个逻辑单元(模块),便于代码管理、复用和封装。
package
一般通过cargo new
创建的就是一个package,包含library crates和 binary crates,也就是.rs
Cargo.lock
Cargo.toml
src
├── bin
│ ├── a.rs
│ └── b.rs
├── lib.rs
└── main.rs
crate
- crate 是 Rust 中最小的编译单元,可以是一个库(library)或可执行程序(binary)。
- 每个 crate 都有一个隐式的根模块(root module):[main.rs](file:///Users/tinachan/rust/hello_cargo/src/main.rs) 或
lib.rs
。 - 所有模块都是从根模块开始组织的。
例如:
src/
├── main.rs // 根模块
├── utils.rs // 模块文件
└── utils/
└── logging.rs // 子模块文件
Moudles
使用 mod
关键字定义一个模块:
mod a {
const num: usize = 1;
fn log1() {
println!("{}", num);
}
}
模块可以嵌套定义:
mod a {
const num: usize = 1;
fn log1() {
println!("{}", num);
}
mod b {
// 无论是func还是mod 在外部需要调用的话都需要加上pub
const num2: usize = 2;
fn log2() {
println!("{}", num2);
}
}
}
导入模块 use
使用 use
将模块或函数引入当前作用域,简化调用:
use a::b::log2;
use a::log1;
fn main() {
log1();
log2(); // 内部的mod需要多层调用
}
重命名 as
[重名的情况]:
use a::{b::log as log1, log as log2};// use合并
fn main() {
log1();
log2();
}
mod a {
const num: usize = 1;
pub fn log() {
println!("{}", num);
}
pub mod b {
// 无论是func还是mod 在外部需要调用的话都需要加上pub
const num2: usize = 2;
pub fn log() {
println!("{}", num2);
}
}
}
可见性(Visibility)
默认模块中的项是私有的 ; pub
关键字实现公有
fn main() {
a::log1();
a::b::log2(); // a内部的mod需要多层调用
}
mod a {
const num: usize = 1;
pub fn log1() {
println!("{}", num);
}
pub mod b {
// 无论是func还是mod 在外部需要调用的话都需要加上pub
const num2: usize = 2;
pub fn log2() {
println!("{}", num2);
}
}
}
pub(crate)
限定在mod a中调用
pub(in crate::a) fn log() {
println!("{}", num);
}
pub(in path)
pub use
Path
通过路径访问
- 绝对路径
crate::b::log();// 绝对路径访问
- 相对路径
a::log();// 相对路径访问
workspace
Rust 中用于管理 多个相关包(crate)的功能。工作区允许将多个 crate 组织在同一仓库下,共享依赖、统一构建 / 测试流程。
可以通过在cargo.toml
中添加
[workspace]
members = ["course","lib_add","lib_divide"]